﻿using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using Internal.KuiHuaBaoDian.Services.Audio;
using KuiHuaBaoDian.Services.Asset;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Pool;
using UnityEngine.ResourceManagement.AsyncOperations;

namespace KuiHuaBaoDian.Services.Audio {

    public static class AudioService {

        /// <summary>
        /// 单例 
        /// </summary>
        public static IAudioService Instance => m_Instance ??= KHBD.TypeMap.Instantiate<IAudioService>();
        private static IAudioService m_Instance;
    }

    public interface IAudioService {

        void Play(IAudioTrigger trigge);
    }

    [Implement(typeof(IAudioService))]
    internal sealed partial class KHBD_AudioService : IAudioService, IService, IAudioServiceInternal {

        private readonly Transform m_CacheTransform;

        private readonly ObjectPool<AudioSource> m_SourcePool;

        private readonly Dictionary<object, string> m_RuntimeKeyDictionary = new();
        private readonly Dictionary<IAudioEvent, SFXEventInstance> m_SFXDictionary = new();
        private readonly IAudioModifier m_SFXModifier = new KHBD_AudioModifier();
        private readonly Dictionary<object, IAudioEvent> m_RuntimeEventDictionary = new();

        private readonly Dictionary<IAudioEvent, EventInstance> m_EventInitializeDictionary = new();

        private EventInstance m_MusicEventInstance;
        private IAudioEvent m_MusicEvent;
        private IAudioContainer m_MusicContainer;
        private AudioSource m_MusicSource;

        public KHBD_AudioService() {
            var gameObject = new GameObject(nameof(AudioService));
            UnityEngine.Object.DontDestroyOnLoad(gameObject);
            gameObject.hideFlags = HideFlags.HideAndDontSave;
            m_CacheTransform = gameObject.transform;
            m_SourcePool = new(CreateSource, collectionCheck: false);
        }

        void IService.OnApplicationQuit() {
            m_SFXDictionary.Clear();
            m_RuntimeEventDictionary.Clear();
            m_RuntimeKeyDictionary.Clear();
            m_EventInitializeDictionary.Clear();
        }

        public void Play(IAudioTrigger trigger) {
            if (trigger == null) {
                return;
            }
            foreach (var eventAsset in trigger.EventAssets) {
                Play(eventAsset).Forget();
            }
        }

        private async UniTaskVoid Play(AssetReference eventAsset) {
            if (eventAsset.RuntimeKeyIsValid()) {
                var runtimeKey = eventAsset.RuntimeKey;
                if (!m_RuntimeEventDictionary.TryGetValue(runtimeKey, out var @event)) {
                    if (!m_RuntimeKeyDictionary.TryGetValue(runtimeKey, out var assetKey)) {
                        m_RuntimeKeyDictionary[runtimeKey] = null;
                        var locationHandle = Addressables.LoadResourceLocationsAsync(runtimeKey);
                        await locationHandle.ToUniTask();
                        if (locationHandle.Status == AsyncOperationStatus.Succeeded && locationHandle.Result.Count > 0) {
                            assetKey = m_RuntimeKeyDictionary[runtimeKey] = locationHandle.Result[0].PrimaryKey;
                            Addressables.Release(locationHandle);
                        } else {
                            m_RuntimeKeyDictionary.Remove(runtimeKey);
                            UnityEngine.Debug.LogError("Failed to load resource locations for GUID: " + runtimeKey);
                            return;
                        }
                    } else if (assetKey == null) {
                        while (m_RuntimeKeyDictionary.ContainsKey(runtimeKey) && m_RuntimeKeyDictionary[runtimeKey] == null) {
                            await UniTask.Yield();
                        }
                        if (!m_RuntimeEventDictionary.ContainsKey(runtimeKey)) {
                            UnityEngine.Debug.LogError("Failed to load resource locations for GUID: " + runtimeKey);
                            return;
                        }
                        assetKey = m_RuntimeKeyDictionary[runtimeKey];
                    }
                    var handle = await AssetService.Instance.Load<KHBD_AudioEvent>(AssetRequest.Instantiate(assetKey), cacheGroup: nameof(AudioService));
                    if (handle == AssetHandle.None) {
                        return;
                    }
                    @event = m_RuntimeEventDictionary[runtimeKey] = handle.Content;
                }
                if (@event == null) {
                    return;
                }
                if (!m_EventInitializeDictionary.TryGetValue(@event, out var eventInstance)) {
                    eventInstance = m_EventInitializeDictionary[@event] = new EventInstance(@event);
                }
                switch (eventInstance.Event.PlayMode) {
                    case AudioPlayMode.Music:
                        PlayMuisic(eventInstance);
                        break;
                    case AudioPlayMode.SFX:
                        PlaySFX(eventInstance);
                        break;
                }
            }
        }

        private void PlayMuisic(EventInstance eventInstance) {
            if (eventInstance == null) {
                return;
            }
            if (m_MusicSource == null) {
                m_MusicSource = m_SourcePool.Get();
                m_MusicSource.loop = true;
            } else {
                m_MusicSource.Stop();
                var clip = m_MusicSource.clip;
                if (clip != null && (clip.loadState is AudioDataLoadState.Loaded or AudioDataLoadState.Loading)) {
                    clip.UnloadAudioData();
                }
                if (m_MusicEventInstance != null) {
                    m_MusicEventInstance.Changed -= MusicEvent_Changed;
                }
            }
            eventInstance.Changed += MusicEvent_Changed;
            m_MusicEventInstance = eventInstance;
            m_MusicEvent = m_MusicEventInstance.Event;
            m_MusicContainer = m_MusicEvent.Containers[0];
            var newClip = m_MusicContainer.Clips[0];
            if (newClip.loadState == AudioDataLoadState.Unloaded) {
                newClip.LoadAudioData();
            }
            m_MusicSource.clip = newClip;
            m_MusicSource.time = 0f;
            m_MusicSource.Play();
            MusicEvent_Changed();
        }

        private void MusicEvent_Changed() {
            var volumeDB = m_MusicEvent.Modifier.Volume + m_MusicEvent.Mixer.FinalVolume + m_MusicContainer.Modifier.Volume;
            //UnityEngine.Debug.Log($"{nameof(MusicEvent_Changed)} (volumeDB={volumeDB}, m_MusicEvent.Modifier.Volume={m_MusicEvent.Modifier.Volume}, m_MusicEvent.Mixer.FinalVolume={m_MusicEvent.Mixer.FinalVolume}, m_MusicContainer.Modifier.Volume={m_MusicContainer.Modifier.Volume})");
            var volume = Mathf.InverseLerp(-60f, 0f, volumeDB);
            volume = Mathf.Clamp01(volume);
            m_MusicSource.volume = volume;
        }

        private void PlaySFX(EventInstance eventInstance) {
            if (eventInstance == null) {
                return;
            }
            if (!m_SFXDictionary.TryGetValue(eventInstance.Event, out var instance)) {
                var containerDictionary = new Dictionary<AudioClip, IAudioContainer>();
                foreach (var container in eventInstance.Event.Containers) {
                    foreach (var clip in container.Clips) {
                        containerDictionary[clip] = container;
                    }
                }
                instance = m_SFXDictionary[eventInstance.Event] = new SFXEventInstance(containerDictionary, m_SourcePool, eventInstance, m_SFXModifier);
                //var limit = 10;
                //var sum = 0;
                //foreach (var kv in m_SFXDictionary.Where(kv => kv.Value.PlayingCount)) {
                //    if (++sum >= limit) {
                //        break;
                //    }
                //}
                //var volume = Mathf.InverseLerp(0, limit, sum);
                //var volumeDB = -36 * Mathf.Clamp01(volume);
                //m_SFXModifier.AsSetter().SetVolume((int)volumeDB);
            }
            instance.Play();
        }

        private AudioSource CreateSource() {
            var gameObject = new GameObject();
            gameObject.transform.parent = m_CacheTransform;
            var source = gameObject.AddComponent<AudioSource>();
            source.playOnAwake = false;
            return source;
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.Audio {

    public interface IAudioServiceInternal {
    }
}
